﻿using System.Collections;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;

namespace Garfield.Presentation.Controls
{
    [TemplatePart(Name = "PART_EditableTextBox", Type = typeof(RichTextBox))]
    [ContentProperty("SuggestionsView")]
    public class SearchAsTypeSelector : Control
    {
        #region SuggestionProvider Dependency Property
        public static readonly DependencyProperty SuggestionProviderProperty = DependencyProperty.Register(
            "SuggestionProvider", typeof (ISuggestionProvider), typeof (SearchAsTypeSelector));

        public ISuggestionProvider SuggestionProvider
        {
            get { return (ISuggestionProvider)GetValue(SuggestionProviderProperty); }
            set { SetValue(SuggestionProviderProperty, value); }
        }
        #endregion

        #region SuggestionsView Related Dependency Properties
        public static readonly DependencyProperty SuggestionsViewProperty = DependencyProperty.Register(
            "SuggestionsView", typeof(ItemsControl), typeof(SearchAsTypeSelector), new PropertyMetadata(OnSuggestionViewsChanged));

        public static readonly DependencyProperty SelectedSuggestionPathProperty = DependencyProperty.Register(
            "SelectedSuggestionPath", typeof(string), typeof(SearchAsTypeSelector), new PropertyMetadata("SelectedItem", OnSelectedSuggestionPathChanged));

        private static readonly DependencyPropertyKey IsSuggestionsOpenPropertyKey =
            DependencyProperty.RegisterReadOnly("IsSuggestionsOpen", typeof(bool), typeof(SearchAsTypeSelector), new PropertyMetadata(false));

        private static readonly DependencyProperty SelectedSuggestionProperty = DependencyProperty.Register("SelectedSuggestion", typeof(string), typeof(SearchAsTypeSelector), new PropertyMetadata(string.Empty));

        public static readonly DependencyProperty IsSuggestionsOpenProperty = IsSuggestionsOpenPropertyKey.DependencyProperty;

        private static void OnSuggestionViewsChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            var searchAsType = (SearchAsTypeSelector)target;
            var oldSuggestionView = (ItemsControl)e.OldValue;
            if (oldSuggestionView != null)
            {
                oldSuggestionView.RemoveHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(searchAsType.OnSelectSuggestionByMouse));
                oldSuggestionView.RemoveHandler(KeyUpEvent, new KeyEventHandler(searchAsType.OnSuggestionsViewKeyUp));
            }
            var newSuggestionView = (ItemsControl)e.NewValue;
            if (newSuggestionView != null)
            {
                newSuggestionView.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(searchAsType.OnSelectSuggestionByMouse));
                newSuggestionView.AddHandler(KeyUpEvent, new KeyEventHandler(searchAsType.OnSuggestionsViewKeyUp));
            }
        }

        private static void OnSelectedSuggestionPathChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            ((SearchAsTypeSelector)target).SetSelectedSuggestionBinding();
        }

        public string SelectedSuggestionPath
        {
            get { return (string)GetValue(SelectedSuggestionPathProperty); }
            set { SetValue(SelectedSuggestionPathProperty, value); }
        }

        public ItemsControl SuggestionsView
        {
            get { return (ItemsControl)GetValue(SuggestionsViewProperty); }
            set { SetValue(SuggestionsViewProperty, value); }
        }

        public bool IsSuggestionsOpen
        {
            get { return (bool)GetValue(IsSuggestionsOpenProperty); }
            private set { SetValue(IsSuggestionsOpenPropertyKey, value); }
        }
        #endregion

        private static readonly DependencyPropertyKey KeywordPropertyKey = DependencyProperty.RegisterReadOnly(
            "Keyword", typeof(string), typeof(SearchAsTypeSelector), new PropertyMetadata(string.Empty, OnKeywordChanged));

        private static void OnKeywordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var selector = (SearchAsTypeSelector) d;
            selector.OnKeywordChanged();
        }

        public static readonly DependencyProperty KeywordProperty = KeywordPropertyKey.DependencyProperty;

        public string Keyword
        {
            get { return (string)GetValue(KeywordProperty); }
            private set { SetValue(KeywordPropertyKey, value); }
        }

        #region SelectedItem related dependency properties
        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
            "SelectedItem",typeof (object),typeof (SearchAsTypeSelector));

        private static readonly DependencyPropertyKey SelectedItemsPropertyKey = DependencyProperty.RegisterReadOnly(
            "SelectedItems", typeof(IList), typeof(SearchAsTypeSelector), new PropertyMetadata(null));

        public static readonly DependencyProperty SelectedItemsProperty = SelectedItemsPropertyKey.DependencyProperty;

        public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register(
            "SelectionMode", typeof(SelectionMode), typeof(SearchAsTypeSelector), new PropertyMetadata(SelectionMode.Single));

        public static readonly DependencyProperty SelectedItemTemplateProperty =
            DependencyProperty.Register("SelectedItemTemplate", typeof(DataTemplate), typeof(SearchAsTypeSelector));


        public object SelectedItem
        {
            get { return GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value);}
        }

        public IList SelectedItems
        {
            get { return (IList) GetValue(SelectedItemsProperty); }
        }

        public SelectionMode SelectionMode
        {
            get { return (SelectionMode) GetValue(SelectionModeProperty); }
            set { SetValue(SelectionModeProperty, value); }
        }

        public DataTemplate SelectedItemTemplate
        {
            get { return (DataTemplate)GetValue(SelectedItemTemplateProperty); }
            set { SetValue(SelectedItemTemplateProperty, value); }
        }
        #endregion

        static SearchAsTypeSelector()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchAsTypeSelector), new FrameworkPropertyMetadata(typeof(SearchAsTypeSelector)));
            FocusableProperty.OverrideMetadata(typeof(SearchAsTypeSelector), new FrameworkPropertyMetadata(false));
        }

        private RichTextBox editableTextBox;
        private BindingExpressionBase selectedSuggestionBinding;
        private Run currentRun;
        private bool isRenderingSuggestions;

        public SearchAsTypeSelector()
        {
            SetSelectedSuggestionBinding();
            SetDefaultSuggestionView();
            SetValue(SelectedItemsPropertyKey, new ObservableCollection<object>());
        }

        private void SetDefaultSuggestionView()
        {
            SuggestionsView = new ListView { SnapsToDevicePixels = true, MaxHeight = 200};
            ScrollViewer.SetVerticalScrollBarVisibility(SuggestionsView, ScrollBarVisibility.Auto);
            VirtualizingStackPanel.SetIsVirtualizing(SuggestionsView, true);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            editableTextBox = (RichTextBox)Template.FindName("PART_EditableTextBox", this);
            editableTextBox.AddHandler(TextBoxBase.TextChangedEvent, new TextChangedEventHandler(OnTextInputChanged));
            editableTextBox.AddHandler(KeyUpEvent, new KeyEventHandler(OnEditableTextBoxKeyUp));
        }

        private void OnEditableTextBoxKeyUp(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Down:
                    SuggestionsView.Focus();
                    SuggestionsView.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
                    break;
            }
            e.Handled = true;
        }

        private void OnSelectSuggestionByMouse(object sender, MouseButtonEventArgs e)
        {
            UpdateSelection();
        }

        private void UpdateSelection()
        {
            isRenderingSuggestions = true;
            selectedSuggestionBinding.UpdateSource();
            var selectedSuggestion = GetValue(SelectedSuggestionProperty);
            if (SelectionMode == SelectionMode.Single)
            {
                editableTextBox.Document.Blocks.Clear();
                var uicontainer =
                    new InlineUIContainer(new ContentControl { Content = selectedSuggestion, ContentTemplate = SelectedItemTemplate }, editableTextBox.Document.ContentStart) {Tag = selectedSuggestion};
                editableTextBox.CaretPosition = uicontainer.ContentEnd;
            }
            else
            {
                ReplacePendingKeywordWithSelectedSuggestion();
            }
            ReSelectItems();
            editableTextBox.Focus();
            IsSuggestionsOpen = false;
            isRenderingSuggestions = false;
        }

        private void ReplacePendingKeywordWithSelectedSuggestion()
        {
            var selectedSuggestion = GetValue(SelectedSuggestionProperty);
            var uicontainer =
                new InlineUIContainer(new ContentControl { Content = selectedSuggestion, ContentTemplate = SelectedItemTemplate }, currentRun.ElementEnd) { Tag = selectedSuggestion };

            var paragraph = (Paragraph) currentRun.Parent;
            paragraph.Inlines.Remove(currentRun);

            editableTextBox.CaretPosition = uicontainer.ContentEnd;
        }

        private void SetSelectedSuggestionBinding()
        {
            var binding = new Binding("SuggestionsView." + SelectedSuggestionPath) {Source = this};
            selectedSuggestionBinding = SetBinding(SelectedSuggestionProperty, binding);
        }

        private void OnTextInputChanged(object sender, TextChangedEventArgs e)
        {
            if (isRenderingSuggestions) return;
            var caretPosition = editableTextBox.CaretPosition;
            var isAdjacentToText  = caretPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text && (caretPosition.Parent is Run);
            if (isAdjacentToText)
            {
                currentRun = ((Run) caretPosition.Parent);
                Keyword = currentRun.Text;
                if (!string.IsNullOrEmpty(Keyword))
                {
                    var suggestions = SuggestionProvider.GetSuggestions(Keyword);
                    SuggestionsView.ItemsSource = suggestions;
                    IsSuggestionsOpen = suggestions.Cast<object>().Count() > 0;
                }
            }
            else
            {
                Keyword = string.Empty;
                SuggestionsView.ItemsSource = new object[0];
                IsSuggestionsOpen = false;
                ReSelectItems();                            
            }
            e.Handled = true;
        }

        private void OnKeywordChanged()
        {
            if (!string.IsNullOrEmpty(Keyword))
            {
                var suggestions = SuggestionProvider.GetSuggestions(Keyword);
                SuggestionsView.ItemsSource = suggestions;
                IsSuggestionsOpen = suggestions.Cast<object>().Count() > 0;
            } else
            {
                SuggestionsView.ItemsSource = new object[0];
                IsSuggestionsOpen = false;
            }
        }


        private void ReSelectItems()
        {
            SelectedItems.Clear();
            var navigator = editableTextBox.Document.ContentStart;
            while (navigator.CompareTo(editableTextBox.Document.ContentEnd) < 0)
            {
                var context = navigator.GetPointerContext(LogicalDirection.Backward);
                if (context == TextPointerContext.ElementStart && navigator.Parent is InlineUIContainer)
                    SelectedItems.Add(((InlineUIContainer)navigator.Parent).Tag);
                navigator = navigator.GetNextContextPosition(LogicalDirection.Forward);
            }
            SelectedItem = SelectionMode == SelectionMode.Single && SelectedItems.Count == 1 ? SelectedItems[0] : null;   
        }

        private void OnSuggestionsViewKeyUp(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Enter:
                    UpdateSelection();
                    break;
            }
            e.Handled = true;
        }
    }
}